
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;

using Derm.Simd;

namespace Derm.Render
{
	/// <summary>
	/// Array buffer object.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class is a <see cref="BufferObject"/> specialized for storing data to be issued to a shader program execution.
	/// </para>
	/// </remarks>
	public class ArrayBufferObject : BufferObject
	{
		#region Constructors

		/// <summary>
		/// Construct an ArrayBufferObject specifying its data layout.
		/// </summary>
		/// <param name="format">
		/// A <see cref="VertexArrayType"/> describing the data layout.
		/// </param>
		/// <param name="hint">
		/// An <see cref="BufferObject.Hint"/> that specifies the data buffer usage hints.
		/// </param>
		public ArrayBufferObject(VertexArrayType format, Hint hint) : base(BufferObjectType.Array, hint)
		{
			// Store array type
			mArrayType = format;
			// Determine base type and item size, if possible
			if (format != VertexArrayType.Complex) {
				mArrayBaseType = ArrayBufferItem.GetArrayBaseType(format);
				mItemSize = ArrayBufferItem.GetArrayItemSize(format);

				SubArrays = new List<SubArrayBuffer>(1);
				SubArrays.Add(new SubArrayBuffer(format));
			}
		}

		#endregion

		#region Array Buffer Information

		/// <summary>
		/// The array buffer object element type.
		/// </summary>
		/// <remarks>
		/// This property is the most compact information about the ArrayBufferObject element
		/// data layout.
		/// </remarks>
		public VertexArrayType ArrayType { get { return (mArrayType); } }

		/// <summary>
		/// The array buffer object element base type.
		/// </summary>
		public VertexBaseType ArrayBaseType { get { return (mArrayBaseType); } }

		/// <summary>
		/// Array buffer object items count.
		/// </summary>
		public uint ItemCount
		{
			get { return (mItemCount); }
			protected set { mItemCount = value; }
		}

		/// <summary>
		/// Array buffer object items size.
		/// </summary>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if <see cref="ArrayType"/> equals to <see cref="VertexArrayType.Complex"/>
		/// </exception>
		public uint ItemSize
		{
			get
			{
				if (mArrayType == VertexArrayType.Complex)
					throw new InvalidOperationException("complex buffer");
				return (mItemSize);
			}
			protected set { mItemSize = value; }
		}

		/// <summary>
		/// Array buffer is interleaved.
		/// </summary>
		public bool Interleaved
		{
			get { return (mInterleaved); }
			protected set { mInterleaved = value; }
		}

		/// <summary>
		/// The array buffer object element type.
		/// </summary>
		private readonly VertexArrayType mArrayType;

		/// <summary>
		/// The array buffer object element base type.
		/// </summary>
		private readonly VertexBaseType mArrayBaseType = VertexBaseType.Undefined;

		/// <summary>
		/// Array buffer object items count.
		/// </summary>
		private uint mItemCount;

		/// <summary>
		/// Data item size, in basic machine units (bytes).
		/// </summary>
		private uint mItemSize;

		/// <summary>
		/// Array buffer is interleaved.
		/// </summary>
		private bool mInterleaved;

		#endregion

		#region Array Buffer Data Definition

		/// <summary>
		/// Define this ArrayBufferObject by specifing only the number of items.
		/// </summary>
		/// <param name="itemsCount">
		/// A <see cref="System.UInt32"/> that specify the number of elements hold by this
		/// ArrayBufferObject.
		/// </param>
		public void Define(uint itemsCount)
		{
			if (itemsCount == 0)
				throw new ArgumentException("invalid", "itemsCount");

			// Store item count
			ItemCount = itemsCount;
			// Allocate buffer
			Allocate(ItemCount * ItemSize);
		}

		/// <summary>
		/// Cast this array buffer object to another one, but having a different array base type.
		/// </summary>
		/// <param name="vertexArrayType"></param>
		/// <returns></returns>
		public ArrayBufferObject Convert(VertexArrayType vertexArrayType)
		{
			if (ArrayBaseType != ArrayBufferItem.GetArrayBaseType(vertexArrayType))
				throw new NotSupportedException("base type mismatch");
			if (ArrayBufferItem.IsArrayVectorType(ArrayType) && ArrayBufferItem.IsArrayVectorType(vertexArrayType))
				throw new NotSupportedException("array length mismatch");
			if (ArrayBufferItem.IsArrayMatrixType(vertexArrayType))
				throw new NotSupportedException("matrix not supported");

			ArrayBufferObject castedObject = new ArrayBufferObject(vertexArrayType, BufferHint);

			// Different item count due different lengths
			castedObject.Define(ItemCount * ArrayBufferItem.GetArrayLength(ArrayType) / ArrayBufferItem.GetArrayLength(vertexArrayType));
			// Memory is copied
			SimdLibrary.MemoryCopy(castedObject.MemoryBuffer.AlignedBuffer, MemoryBuffer.AlignedBuffer, MemoryBuffer.Size);

			return (castedObject);
		}

		#endregion

		#region Array Buffer Data Access

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="index"></param>
		/// <returns></returns>
		public T GetData<T>(uint index) where T : struct
		{
			IntPtr bufferPtr = new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + index * Marshal.SizeOf(typeof(T)));

			return ((T)Marshal.PtrToStructure(bufferPtr, typeof(T)));
		}

		#endregion

		#region Sub-Array Abstraction

		/// <summary>
		/// Sub-array information.
		/// </summary>
		/// <remarks>
		/// Using this buffer fragmentation, it is possible to define multiple buffers allocated into a single
		/// one (packing and/or inteleaving).
		/// </remarks>
		[DebuggerDisplay("SubArrayBuffer Offset={ArrayOffset} Stride={ArrayStride}")]
		protected internal class SubArrayBuffer : ArrayBufferItem
		{
			/// <summary>
			/// Construct an SubArrayBuffer from a <see cref="ArrayBufferItemAttribute"/>.
			/// </summary>
			/// <param name="attribute">
			/// A <see cref="ArrayBufferItemAttribute"/> describing the vertex array buffer item.
			/// </param>
			public SubArrayBuffer(ArrayBufferItemAttribute attribute) : base(attribute) { }

			/// <summary>
			/// Construct an SubArrayBuffer from a <see cref="VertexArrayType"/>.
			/// </summary>
			/// <param name="vertexArrayType">
			/// A <see cref="VertexArrayType"/> that synthetize all informations about a vertex array buffer item.
			/// </param>
			public SubArrayBuffer(VertexArrayType vertexArrayType) : base(vertexArrayType)
			{
				
			}

			/// <summary>
			/// The offset for accessing to the sub-array.
			/// </summary>
			public IntPtr ArrayOffset;

			/// <summary>
			/// The stride for accessing to the next sub-array item.
			/// </summary>
			public uint ArrayStride;
		}

		/// <summary>
		/// Number of vertex streams specified in this ArrayBufferObject.
		/// </summary>
		protected internal uint SubArrayCount { get { return ((uint)SubArrays.Count); } }

		/// <summary>
		/// Information about array buffer vertex stream.
		/// </summary>
		internal SubArrayBuffer GetSubArrayInfo(uint streamIndex)
		{
			return (SubArrays[(int)streamIndex]);
		}

		/// <summary>
		/// Sub-array compositing this ArrayBufferObject.
		/// </summary>
		protected List<SubArrayBuffer> SubArrays;

		/// <summary>
		/// Detect array configuration of an array buffer item.
		/// </summary>
		/// <param name="type"></param>
		/// <param name="interleaved"></param>
		/// <returns></returns>
		protected List<SubArrayBuffer> DetectSubArrays(Type type, out bool interleaved)
		{
			// Test for simple buffer
			SubArrayBuffer subarray = DetectSubArray(type);
			if (subarray != null) {
				List<SubArrayBuffer> uniqueArray = new List<SubArrayBuffer>(1);
				uniqueArray.Add(subarray);
				interleaved = false;
				return (uniqueArray);
			}

			// Test for interleaved sub arrays
			List<SubArrayBuffer> subArrays = DetectInterleavedSubArrays(type);
			if (subArrays != null) {
				interleaved = true;
				return (subArrays);
			}

			interleaved = false;
			return (null);
		}

		/// <summary>
		/// Detect sub-array configuration of a structure (interleaved sub-arrays)
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		protected List<SubArrayBuffer> DetectInterleavedSubArrays(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			FieldInfo[] typeFields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);

			if (typeFields.Length == 0)
				throw new InvalidOperationException(String.Format("type {0} has no public field", type.FullName));

			List<SubArrayBuffer> subArrays = new List<SubArrayBuffer>();
			uint structStride = (uint)Marshal.SizeOf(type);

			foreach (FieldInfo typeField in typeFields) {
				SubArrayBuffer subarray = DetectSubArray(typeField.FieldType);

				// Sub-array must have
				if (subarray == null)
					throw new InvalidOperationException(String.Format("field {0}.{1} is not a array buffer item", type.FullName, typeField.Name));

				// Determine sub-array offset
				subarray.ArrayOffset = Marshal.OffsetOf(type, typeField.Name);
				subarray.ArrayStride = structStride;

				// Collect sub-array
				subArrays.Add(subarray);
			}

			return (subArrays);
		}

		protected SubArrayBuffer DetectSubArray(Type type)
		{
			ArrayBufferItemAttribute itemAttribute = (ArrayBufferItemAttribute)Attribute.GetCustomAttribute(type, typeof(ArrayBufferItemAttribute));

			if (itemAttribute == null) {
				if (type.IsValueType && type.IsPrimitive) {
					if      (type == typeof(float))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.Float, 1);
					else if (type == typeof(double))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.Double, 1);
					else if (type == typeof(int))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.Int, 1);
					else if (type == typeof(short))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.Short, 1);
					else if (type == typeof(uint))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.UInt, 1);
					else if (type == typeof(ushort))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.UShort, 1);
					else if (type == typeof(byte))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.UByte, 1);
					else if (type == typeof(char))
						itemAttribute = new ArrayBufferItemAttribute(VertexBaseType.Byte, 1);
					else
						return (null);

					Debug.Assert(itemAttribute != null);

					return (new SubArrayBuffer(itemAttribute));
				} else
					return (null);
			} else
				return (new SubArrayBuffer(itemAttribute));
		}

		#endregion

		#region Array Buffer Conversion

		/// <summary>
		/// Copy data from any source supported.
		/// </summary>
		/// <param name="array">
		/// The source array where the data comes from.
		/// </param>
		/// <param name="count">
		/// A <see cref="System.UInt32"/> that specify the number of items to copy. The items to copy are referred in terms
		/// of the data layout of this <see cref="ArrayBufferObject"/>, not of the element type of <paramref name="array"/>!
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="array"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="count"/> equals to 0.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if the data layout of this ArrayBufferObject is <see cref="VertexArrayType.Complex"/>.
		/// </exception>
		/// <exception cref="NotSupportedException">
		/// Exception thrown if the rank of <paramref name="array"/> is different from 1.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if it is not possible to determine the element type of the array <paramref name="array"/>, or if the base type of
		/// the array elements is not compatible with the base type of this <see cref="ArrayBufferObject"/>.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if the length of <paramref name="array"/> is not sufficient to copy <paramref name="count"/> elements of this
		/// <see cref="ArrayBufferObject"/>.
		/// </exception>
		public void Copy(Array array, uint count)
		{
			if (array == null)
				throw new ArgumentNullException("array");
			if (count == 0)
				throw new ArgumentException("zero count", "count");
			if (mArrayType == VertexArrayType.Complex)
				throw new InvalidOperationException("complex buffer");

			if (array.Rank != 1)
				throw new NotSupportedException(String.Format("copying from array of rank {0} not supported", array.Rank));

			Type arrayElementType = array.GetType().GetElementType();

			if (arrayElementType == null)
				throw new InvalidOperationException("unknown array element type");

			VertexArrayType arrayElementVertexType = ArrayBufferItem.GetArrayType(arrayElementType);

			if (mArrayBaseType != ArrayBufferItem.GetArrayBaseType(arrayElementVertexType))
				throw new InvalidOperationException(String.Format("source base type of {0} incompatible with destination base type of {1}", arrayElementType.Name, mArrayBaseType));

			// Here the length of the vector is not considered.
			// The important thing is the data length check to avoid buffer overruns

			uint arrayItemSize = ArrayBufferItem.GetArrayItemSize(arrayElementVertexType);

			if (ItemSize * count > arrayItemSize * array.Length)
				throw new InvalidOperationException("array buffer overrun");

			GCHandle ppData = GCHandle.Alloc(array, GCHandleType.Pinned);
			try {
				unsafe {
					byte* arrayPtr = (byte*) ppData.AddrOfPinnedObject().ToPointer();
					byte* dstPtr = (byte*) MemoryBuffer.AlignedBuffer.ToPointer();

					for (uint i = 0; i < count; i++, arrayPtr += arrayItemSize, dstPtr += ItemSize)
						SimdLibrary.MemoryCopy(dstPtr, arrayPtr, ItemSize);
				}
			} finally {
				ppData.Free();
			}
		}

		/// <summary>
		/// Copy from an ArrayBufferObject with an indirection defined by an index.
		/// </summary>
		/// <param name="buffer">
		/// An <see cref="ArrayBufferObject"/> that specify the source data buffer to copy.
		/// </param>
		/// <param name="indices">
		/// An array of indices indicating the order of the vertices copied from <paramref name="buffer"/>.
		/// </param>
		/// <param name="count">
		/// A <see cref="System.UInt32"/> that specify how many elements to copy from <paramref name="buffer"/>.
		/// </param>
		/// <param name="offset">
		/// A <see cref="System.UInt32"/> that specify the first index considered from <paramref name="indices"/>. A
		/// value of 0 indicates that the indices are considered from the first one.
		/// </param>
		/// <param name="stride">
		/// A <see cref="System.UInt32"/> that specify the offset between two indexes considered for the copy operations
		/// from <paramref name="indices"/>. A value of 1 indicates that all considered indices are contiguos.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="buffer"/> or <paramref name="indices"/> are null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="count"/> or <paramref name="stride"/> equals to 0.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if the combination of <paramref name="count"/>, <paramref name="offset"/> and
		/// <paramref name="stride"/> will cause a <paramref name="indices"/> array access out of its bounds.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if this <see cref="ArrayBufferObject"/> have a complex data layout, of it has a vertex
		/// base type different from <paramref name="buffer"/>.
		/// </exception>
		/// <remarks>
		/// <para>
		/// After a successfull copy operation, the previous buffer is discarded replaced by the copied buffer from
		/// <paramref name="buffer"/>.
		/// </para>
		/// </remarks>
		public void Copy(ArrayBufferObject buffer, uint[] indices, uint count, uint offset, uint stride)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (indices == null)
				throw new ArgumentNullException("indices");
			if (count == 0)
				throw new ArgumentException("invalid", "count");
			if (stride == 0)
				throw new ArgumentException("invalid", "stride");
			if (offset + ((count - 1) * stride) > indices.Length)
				throw new InvalidOperationException("indices out of bounds");

			if (mArrayType == VertexArrayType.Complex)
				throw new InvalidOperationException("complex buffer");
			if (mArrayBaseType != buffer.mArrayBaseType)
				throw new InvalidOperationException("base type mismatch");

			Define(count);

			unsafe {
				byte* dstPtr = (byte*)MemoryBuffer.AlignedBuffer.ToPointer();

				for (uint i = 0; i < count; i++, dstPtr += ItemSize) {
					uint arrayIndex = indices[(i * stride) + offset];

					// Position 'srcPtr' to the indexed element
					byte* srcPtr = ((byte*)buffer.MemoryBuffer.AlignedBuffer.ToPointer()) + (ItemSize * arrayIndex);

					// Copy the 'arrayIndex'th element
					SimdLibrary.MemoryCopy(dstPtr, srcPtr, ItemSize);
				}
			}

			ItemCount = count;
		}

		/// <summary>
		/// Copy from an ArrayBufferObject with an indirection defined by an index (polygon tessellation).
		/// </summary>
		/// <param name="buffer">
		/// An <see cref="ArrayBufferObject"/> that specify the source data buffer to copy.
		/// </param>
		/// <param name="vcount">
		/// An array of integers indicating the number of the vertices of the polygon copied from <paramref name="buffer"/>. This parameter
		/// indicated how many polygons to copy (the array length). Each item specify the number of vertices composing the polygon.
		/// </param>
		/// <param name="indices">
		/// An array of indices indicating the order of the vertices copied from <paramref name="buffer"/>.
		/// </param>
		/// <param name="offset">
		/// A <see cref="System.UInt32"/> that specify the first index considered from <paramref name="indices"/>. A
		/// value of 0 indicates that the indices are considered from the first one.
		/// </param>
		/// <param name="stride">
		/// A <see cref="System.UInt32"/> that specify the offset between two indexes considered for the copy operations
		/// from <paramref name="indices"/>. A value of 1 indicates that all considered indices are contiguos.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="buffer"/>, <paramref name="indices"/> or <see cref="vcount"/> are null.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if this <see cref="ArrayBufferObject"/> have a complex data layout, of it has a vertex
		/// base type different from <paramref name="buffer"/>.
		/// </exception>
		/// <remarks>
		/// <para>
		/// After a successfull copy operation, the previous buffer is discarded replaced by the copied buffer from
		/// <paramref name="buffer"/>.
		/// </para>
		/// </remarks>
		public void Copy(ArrayBufferObject buffer, uint[] indices, uint[] vcount, uint offset, uint stride)
		{
			if (buffer == null)
				throw new ArgumentNullException("buffer");
			if (indices == null)
				throw new ArgumentNullException("indices");
			if (vcount == null)
				throw new ArgumentNullException("indices");
			if (stride == 0)
				throw new ArgumentException("invalid", "stride");

			if (mArrayType == VertexArrayType.Complex)
				throw new InvalidOperationException("complex buffer");
			if (mArrayBaseType != buffer.mArrayBaseType)
				throw new InvalidOperationException("base type mismatch");

			// Allocate array buffer
			uint minVertices = UInt32.MaxValue, maxVertices = UInt32.MinValue;

			Array.ForEach(vcount, delegate(uint v) {
          		minVertices = Math.Min(v, minVertices);
				maxVertices = Math.Max(v, maxVertices);
          	});

			if ((minVertices < 3) && (maxVertices >= 3))
				throw new ArgumentException("ambigous polygons set", "vcount");

			uint totalVerticesCount = 0;

			Array.ForEach(vcount, delegate(uint v) {
				if (v == 4) {
          			totalVerticesCount += 6;			// Triangulate quad with two triangles
				} else if (v > 4) {
					totalVerticesCount += (v - 2) * 3;	// Triangulate as if it is a polygon
				} else {
					Debug.Assert(v == 3);
					totalVerticesCount += 3;			// Exactly a triangle
				}
			});

			Define(totalVerticesCount);

			// Copy polygons (triangulate)
			uint count = 0;

			unsafe {
				byte* dstPtr = (byte*)MemoryBuffer.AlignedBuffer.ToPointer();
				uint indicesIndex = offset;

				for (uint i = 0; i < vcount.Length; i++) {
					uint verticesCount = vcount[i];
					uint[] verticesIndices;

					if (verticesCount == 4) {
						verticesIndices = new uint[6];
						verticesIndices[0] = indices[indicesIndex + (0 * stride)];
						verticesIndices[1] = indices[indicesIndex + (1 * stride)];
						verticesIndices[2] = indices[indicesIndex + (2 * stride)];
						verticesIndices[3] = indices[indicesIndex + (0 * stride)];
						verticesIndices[4] = indices[indicesIndex + (2 * stride)];
						verticesIndices[5] = indices[indicesIndex + (3 * stride)];

						indicesIndex += 4 * stride;
					} else if (verticesCount > 4) {
						uint triCount = verticesCount - 2;
						uint pivotIndex = indicesIndex;

						verticesIndices = new uint[triCount * 3];

						// Copy polygon indices
						for (uint tri = 0; tri < triCount; tri++) {
							verticesIndices[tri * 3 + 0] = indices[pivotIndex];
							verticesIndices[tri * 3 + 1] = indices[indicesIndex + (tri + 2) * stride];
							verticesIndices[tri * 3 + 2] = indices[indicesIndex + (tri + 1) * stride];
						}

						indicesIndex += verticesCount * stride;
					} else {
						verticesIndices = new uint[verticesCount];
						for (int j = 0; j < verticesCount; j++, indicesIndex += stride)
							verticesIndices[j] = indices[indicesIndex];
					}

					count += (uint) verticesIndices.Length;

					for (uint j = 0; j < verticesIndices.Length; j++, dstPtr += ItemSize) {
						// Position 'srcPtr' to the indexed element
						byte* srcPtr = ((byte*) buffer.MemoryBuffer.AlignedBuffer.ToPointer()) + (ItemSize * verticesIndices[j]);
						// Copy the 'arrayIndex'th element
						SimdLibrary.MemoryCopy(dstPtr, srcPtr, ItemSize);
					}
				}
			}

			ItemCount = count;
		}

		#endregion
	}

	/// <summary>
	/// Array buffer object.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public sealed class ArrayBufferObject<T> : ArrayBufferObject where T : struct
	{
		#region Constructors

		/// <summary>
		/// Construct an ArrayBufferObject.
		/// </summary>
		/// <param name="hint">
		/// An <see cref="BufferObject.Hint"/> that specifies the data buffer usage hints.
		/// </param>
		public ArrayBufferObject(Hint hint) : base(ArrayBufferItem.GetArrayType(typeof(T)), hint)
		{
			bool interleaved;

			// Determine basic type information
			SubArrays = DetectSubArrays(typeof(T), out interleaved);
			// The item is represented by 'T'
			ItemSize = (uint)Marshal.SizeOf(typeof(T));
			Interleaved = interleaved;
		}

		#endregion

		#region Array Buffer Data Definition

		/// <summary>
		/// Define this ArrayBufferObject by specifing an array of <typeparamref name="T"/>.
		/// </summary>
		/// <param name="items">
		/// An array that specify the contents of this ArrayBufferObject.
		/// </param>
		public void Define(T[] items)
		{
			if (items == null)
				throw new ArgumentNullException("items");
			if (items.Length == 0)
				throw new ArgumentException("zero items", "items");
			if ((BufferHint != Hint.StaticCpuDraw) && (BufferHint != Hint.DynamicCpuDraw))
				throw new InvalidOperationException(String.Format("conflicting hint {0}", BufferHint));

			// Store item count
			Define((uint)items.Length);
			// Copy the buffer
			SimdLibrary.MemoryCopy(MemoryBuffer.AlignedBuffer, items, ItemCount * ItemSize);
		}

		#endregion

		#region Array Buffer Access

		/// <summary>
		/// Accessor to ArrayBufferObject items.
		/// </summary>
		/// <param name="index"></param>
		/// <returns></returns>
		public T this[uint index]
		{
			get
			{
				if ((MemoryBuffer == null) || (MemoryBuffer.AlignedBuffer == IntPtr.Zero))
					throw new InvalidOperationException("not defined");
				if (index >= ItemCount)
					throw new ArgumentException("index out of bounds", "index");

				return ((T) Marshal.PtrToStructure(new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + (index * ItemSize)), typeof(T)));
			}
			set
			{
				if ((MemoryBuffer == null) || (MemoryBuffer.AlignedBuffer == IntPtr.Zero))
					throw new InvalidOperationException("not defined");
				if (index >= ItemCount)
					throw new ArgumentException("index out of bounds", "index");

				Marshal.StructureToPtr(value, new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + (index * ItemSize)), false);
			}
		}

		#endregion

		#region Join Multiple ArrayBufferObject

		/// <summary>
		/// Join a set of compatible buffers.
		/// </summary>
		/// <param name="buffers">
		/// An array of <see cref="ArrayBufferObject"/> instances to be packed sequentially on this ArrayBufferObject.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="buffers"/> is null or any item in <paramref name="buffers"/> array
		/// is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="buffers"/> length is less than two (minimum number of ArrayBufferObject
		/// to join).
		/// </exception>
		/// <remarks>
		/// <para>
		/// This ArrayBufferObject will store sequentially all the items stored in <paramref name="buffers"/>.
		/// </para>
		/// </remarks>
		public void Join(params ArrayBufferObject<T>[] buffers)
		{
			if (buffers == null)
				throw new ArgumentNullException("buffers");
			if (buffers.Length <= 1)
				throw new ArgumentException("not enought buffers", "buffers");

			// Since T is the same for 'buffers' and 'this', they are compatible for every T, even if
			// T is a complex structure

			// Collect array buffer objects statistics
			uint bufferSize = 0, itemCount = 0;

			for (int i = 0; i < buffers.Length; i++) {
				if (buffers[i] == null)
					throw new ArgumentNullException("buffers[" + i + "]");

				bufferSize += buffers[i].BufferSize;
				itemCount += buffers[i].ItemCount;
			}

			// Allocate the array buffer object
			Allocate(bufferSize);

			// Join buffers into a single one
			bufferSize = 0;
			for (int i = 0; i < buffers.Length; i++) {
				IntPtr src = buffers[i].MemoryBuffer.AlignedBuffer;
				IntPtr dst = new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + bufferSize);

				// Copy 'i'th buffer content
				SimdLibrary.MemoryCopy(dst, src, buffers[i].BufferSize);
				// Next buffer offset
				bufferSize += buffers[i].BufferSize;
			}

			// Item count is the sum of the items found in 'buffers'
			ItemCount = itemCount;
		}

		#endregion
	}

	/// <summary>
	/// An packed or interleaved set of buffer objects.
	/// </summary>
	/// <remarks>
	/// <para>
	/// 
	/// </para>
	/// </remarks>
	public sealed class ComplexArrayBufferObject : ArrayBufferObject
	{
		#region Constructors

		/// <summary>
		/// Construct an ArrayBufferObject.
		/// </summary>
		/// <param name="hint">
		/// An <see cref="BufferObject.Hint"/> that specifies the data buffer usage hints.
		/// </param>
		public ComplexArrayBufferObject(Hint hint) : base(VertexArrayType.Complex, hint)
		{
			ItemSize = 0;
			ItemCount = 0;
		}

		#endregion

		#region Packing & Interleaving

		/// <summary>
		/// Pack a set of buffers.
		/// </summary>
		/// <param name="buffers">
		/// An array of <see cref="ArrayBufferObject"/> instances to be packed sequentially on this ArrayBufferObject.
		/// </param>
		/// <remarks>
		/// Each buffer specified in <paramref name="buffers"/> will be copied into this ArrayBufferObject as sub-array.
		/// The order of sub-arrays is defined by the order of the ArrayBufferObject instances in <paramref name="buffers"/>.
		/// </remarks>
		public void Pack(params ArrayBufferObject[] buffers)
		{
			if (buffers == null)
				throw new ArgumentNullException("buffers");
			if (buffers.Length <= 1)
				throw new ArgumentException("not enought buffers", "buffers");

			// Collect buffer statistics
			uint bufferOffset = 0;
			uint bufferSize = 0, bufferItemsCount = buffers[0].ItemCount, bufferItemSize = 0;

			for (int i = 0; i < buffers.Length; i++) {
				if (buffers[i] == null)
					throw new ArgumentNullException("buffers["+i+"]");
				if (buffers[i].ItemCount != bufferItemsCount)
					throw new ArgumentException("items count mismatch", "buffers["+i+"]");
				if (buffers[i].Interleaved == true)
					throw new ArgumentException("interleaved buffer", "buffers["+i+"]");

				// Determine total buffer size
				bufferSize += buffers[i].BufferSize;
				// Determine total item size (not really useful)
				bufferItemSize += buffers[i].ItemSize;
			}

			// Define packed buffer layout
			List<SubArrayBuffer> packedArrays = new List<SubArrayBuffer>();

			for (int i = 0; i < buffers.Length; i++) {
				SubArrayBuffer sourceArray = buffers[i].GetSubArrayInfo(0);
				ArrayBufferItemAttribute arrayBufferAttrs = new ArrayBufferItemAttribute(sourceArray.BaseType, sourceArray.ArrayLength);
				SubArrayBuffer packedArray = new SubArrayBuffer(arrayBufferAttrs);

				packedArray.ArrayOffset = new IntPtr(bufferOffset);
				packedArrays.Add(packedArray);

				bufferOffset += buffers[i].BufferSize;
			}

			// Pack data
			Allocate(bufferSize);

			bufferSize = 0;
			for (int i = 0; i < buffers.Length; i++) {
				IntPtr src = new IntPtr(buffers[i].MemoryBuffer.AlignedBuffer.ToInt64());
				IntPtr dst = new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + bufferSize);

				SimdLibrary.MemoryCopy(dst, src, buffers[i].BufferSize);

				bufferSize += buffers[i].BufferSize;
			}

			// Define this packed buffer
			SubArrays = packedArrays;
			ItemCount = bufferItemsCount;
			ItemSize = bufferItemSize;
			Interleaved = false;
		}

		/// <summary>
		/// Interleave a set of buffers.
		/// </summary>
		/// <param name="buffers">
		/// A set of <see cref="ArrayBufferObject"/> which has to be interleaved with each other.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="buffers"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="buffers"/> length is less than two.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if one of the items in <paramref name="buffers"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if one of the items in <paramref name="buffers"/> is already inteleaved.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if not all the items in <paramref name="buffers"/> has the same items count.
		/// </exception>
		public void Interleave(params ArrayBufferObject[] buffers)
		{
			if (buffers == null)
				throw new ArgumentNullException("buffers");
			if (buffers.Length <= 1)
				throw new ArgumentException("not enought buffers", "buffers");

			// Collect buffer statistics
			uint itemsCount = buffers[0].ItemCount;
			uint itemSize = 0;
			uint[] itemOffset = new uint[buffers.Length];

			for (int i = 0; i < buffers.Length; i++) {
				if (buffers[i] == null)
					throw new ArgumentNullException("buffers["+i+"]");
				if (buffers[i].Interleaved == true)
					throw new ArgumentException("interleaved buffer", "buffers["+i+"]");
				if (buffers[i].ItemCount != itemsCount)
					throw new ArgumentException("items count mismatch", "buffers["+i+"]");

				// Determine interleaved field offset
				itemOffset[i] = itemSize;
				// Collect item size
				itemSize += buffers[i].ItemSize;
			}

			// Define interleaved buffer layout
			List<SubArrayBuffer> interleavedArrays = new List<SubArrayBuffer>();

			for (int i = 0; i < buffers.Length; i++) {
				SubArrayBuffer sourceArray = buffers[i].GetSubArrayInfo(0);
				ArrayBufferItemAttribute arrayBufferAttrs = new ArrayBufferItemAttribute(sourceArray.BaseType, sourceArray.ArrayLength);
				SubArrayBuffer interleavedArray = new SubArrayBuffer(arrayBufferAttrs);

				interleavedArray.ArrayOffset = new IntPtr(itemOffset[i]);
				interleavedArray.ArrayStride = itemSize;

				interleavedArrays.Add(interleavedArray);
			}

			// Interleave data
			Allocate(itemSize * itemsCount);

			for (int i = 0; i < itemsCount; i++) {
				for (int j = 0; j < buffers.Length; j++) {
					IntPtr src = new IntPtr(buffers[j].MemoryBuffer.AlignedBuffer.ToInt64() + (buffers[j].ItemSize * i));
					IntPtr dst = new IntPtr(MemoryBuffer.AlignedBuffer.ToInt64() + (i * itemSize) + interleavedArrays[j].ArrayOffset.ToInt64());

					SimdLibrary.MemoryCopy(dst, src, buffers[j].ItemSize);
				}
			}

			// Define this interleaved buffer
			SubArrays = interleavedArrays;
			ItemCount = itemsCount;
			Interleaved = true;
		}

		#endregion
	}
}